//
//  HTCarousel.swift
//  MagaCore
//
//  Created by MrMessy on 2023/5/25.
//

import SwiftUI

public struct HTCarousel<Item, ItemView: View> : View {

  @Binding var var_items: [Item]
  @Binding var var_currentIndex: Int
  {
    didSet {
      if var_currentIndex >= var_items.count {
        var_currentIndex = var_currentIndex % var_items.count
      } else if var_currentIndex < 0 {
        var_currentIndex = var_items.count - 1
      }
    }
  }

  @State private var var_timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()

  var var_spacing: CGFloat = 0
  var var_itemWidth: CGFloat = 0
  var var_itemHeight: CGFloat = 0
  var var_opacity: Double = 0.0
  var var_sizeScale: Double = 0.0
  var var_itemView: (Item) -> ItemView
  var var_autoScrollTime: TimeInterval = 0

  public init(var_items: Binding<[Item]>, var_currentIndex: Binding<Int>,  var_spacing: CGFloat, var_itemWidth: CGFloat, var_itemHeight: CGFloat, var_opacity: Double, var_sizeScale: Double, var_autoScrollTime: TimeInterval = 0, @ViewBuilder var_itemView: @escaping (Item) -> ItemView) {
    self._var_items = var_items
    self._var_currentIndex = var_currentIndex
    self.var_spacing = var_spacing
    self.var_itemWidth = var_itemWidth
    self.var_itemHeight = var_itemHeight
    self.var_opacity = var_opacity
    self.var_sizeScale = var_sizeScale
    self.var_itemView = var_itemView
    self.var_autoScrollTime = var_autoScrollTime
  }

  public var body: some View {
    VStack {
      GeometryReader { proxy in
        ZStack() {
          ForEach (var_items.indices, id: \.self) { index in
            var_itemView(var_items[index])
              .frame(width: var_itemWidth, height: var_itemHeight)
              .id(index)
            .scaleEffect(max(1.0 - abs(ht_distance(index)) * var_sizeScale, 0.0001))
            .zIndex(1.0 - abs(ht_distance(index) * 0.1))
            .offset(x: ht_xOffset(index), y: 0)
            .onTapGesture {
              withAnimation {
                var_currentIndex = index
              }
            }
          }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .onReceive(var_timer) { time in
          if var_autoScrollTime != 0 {
            withAnimation {
              var_currentIndex += 1
            }
          }
        }
      }
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .onAppear {
      self.var_timer = Timer.publish(every: var_autoScrollTime, on: .main, in: .common).autoconnect()
    }
    .onDisappear {
      self.var_timer.upstream.connect().cancel()
    }
    .gesture(
      DragGesture()
        .onChanged { value in
          if var_autoScrollTime != 0 {
            var_timer.upstream.connect().cancel()
            var_timer = Timer.publish(every: var_autoScrollTime, on: .main, in: .common).autoconnect()
          }
        }
        .onEnded { value in
          let threshold: CGFloat = UIDevice.ht_fixToWidth(width: 50)
          if value.translation.width > threshold {
            withAnimation {
              var_currentIndex -= 1
            }
          } else if value.translation.width < -threshold {
            withAnimation {
              var_currentIndex += 1
            }
          }
          let haptic = UIImpactFeedbackGenerator(style: .soft)
          haptic.impactOccurred()
        }
    )
    .frame(height: var_itemHeight)
  }

  private func ht_distance(_ index: Int) -> Double {
    return Double(var_currentIndex - index).remainder(dividingBy: Double(var_items.count))
  }

  private func ht_xOffset(_ index: Int) -> CGFloat {
    let var_offset: CGFloat = UIDevice.ht_fixToWidth(width: 60)
    if (var_currentIndex == 0) {
      if (index == var_items.count - 1) {
        return CGFloat(-1) * var_offset
      } else if (index == var_items.count - 2) {
        return CGFloat(-2) * var_offset
      } else {
        return CGFloat(index - var_currentIndex) * var_offset
      }
    } else if (var_currentIndex == 1) {
      if (index == var_items.count - 1) {
        return CGFloat(-2) * var_offset
      } else {
        return CGFloat(index - var_currentIndex) * var_offset
      }
    } else if (var_currentIndex == var_items.count - 1) {
      if (index == 0) {
        return CGFloat(1) * var_offset
      } else if (index == 1) {
        return CGFloat(2) * var_offset
      } else {
        return CGFloat(index - var_currentIndex) * var_offset
      }
    } else if (var_currentIndex == var_items.count - 2) {
      if (index == 0) {
        return CGFloat(2) * var_offset
      } else {
        return CGFloat(index - var_currentIndex) * var_offset
      }
    } else {
      return CGFloat(index - var_currentIndex) * var_offset
    }
  }
}
